package com.alisls.shequtao.service.goods.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.List;

/**
 * 资源服务器配置类
 * 注解 @EnableResourceServer 开启资源服务器（获取资源服务器中的资源，必须带有token才能访问）
 * 注解 @EnableGlobalMethodSecurity(prePostEnabled = true) 开启方法级别权限控制
 *
 * @author Ke Wang
 */
@Configuration
@RefreshScope
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Slf4j
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    /**
     * SpringSecurity资源服务器的资源ID
     */
    @Value("${oauth.resource.resource-id}")
    private String resourceId;

    /**
     * 客户端标识
     */
    @Value("${oauth.client-id}")
    private String clientId;

    /**
     * 客户端密码
     */
    @Value("${oauth.secret}")
    private String secret;

    /**
     * Token验证地址
     */
    @Value("${oauth.check-token-url}")
    private String checkTokenUrl;

    /**
     * Spring Security Scope属性
     */
    @Value("${oauth.scope}")
    private String scope;

    /**
     * 匿名访问的url
     */
    @Value("#{'${service.product.ignore-urls}'.split(',')}")
    private List<String> ignoreUrls;

    /**
     * 属性配置类
     */
    @Resource
    private ServiceProductProp serviceProductProp;

    /**
     * JwtTokenStore
     */
    @Resource
    private TokenStore jwtTokenStore;

    /**
     * 资源配置
     * 1.配置当前资源服务器的ID
     * 2.配置当前资源服务器token验证服务
     * @param resources 资源
     * @throws Exception 异常
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        if (log.isInfoEnabled()) {
            log.info("资源服务器ID为{}", resourceId);
        }

        /*
         * 设置当前资源服务器的ID；
         * 认证服务会认证客户端有没有访问这个资源ID的权限
         */
        resources.resourceId(resourceId);

        /*
         * 设置token服务
         * 使用远程调用认证服务器端点/oauth/check_token方式校验token，这里配置的服务指定了在哪里验证token
         */
        // resources.tokenServices(tokenService());

        /**
         * 设置tokenStore
         * 使用本地JwtToken方式校验token
         */
        resources.tokenStore(jwtTokenStore);
    }

    /**
     * 建一个远程TokenService（即连接到认证服务器的进行token校验的服务）
     * @return ResourceServerTokenServices
     */
    public ResourceServerTokenServices tokenService() {
        if (log.isInfoEnabled()) {
            log.info("Token校验端点为{}", checkTokenUrl);
            log.info("客户端ID为{}", clientId);
            log.info("客户端密码为{}", secret);
        }

        // 创建一个远程TokenService（即连接到认证服务器的进行token校验的服务），需求设置该端点认证后可访问
        RemoteTokenServices tokenService = new RemoteTokenServices();
        // 设置检查令牌的端点：/oauth/check_token，该端点在认证服务器中配置需要认证通过才可以访问，所有还需提供ClientId和ClientSecret
        tokenService.setCheckTokenEndpointUrl(checkTokenUrl);
        // 配置进行token校验时，即访问认证服务器token校验端点/oauth/check_token时，请求头中提供的客户端的ID
        tokenService.setClientId(clientId);
        // 配置进行token校验时，即访问认证服务器token校验端点/oauth/check_token时，请求头中提供的客户端密码
        tokenService.setClientSecret(secret);
        return tokenService;
    }

    /**
     * 控制令牌范围权限和授权规则
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        // 设定Session策略，SpringSecurity不会创建HttpSession实例，也不会使用
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // 设置可以匿名访问的资源
        setIgnoreUrls(http);

        // 设置资源访问授权规则
        http.authorizeRequests()
                // 设置访问资源/user/getUserWithPwdByUsername/*可以匿名访问
                //.antMatchers("/user/getUserWithPwdByUsername/*").anonymous()
                // 设置访问资源/product/listProducts需要权限product:listProducts（注意顺序，不能放到后面来写）
                //.antMatchers("/product/listProducts").hasAuthority("product:listProducts")
                // 设定资源服务器要求所有请求都必须有scope范围(在认证服务器上针对客户端配置的范围)
                .antMatchers("/**").access("#oauth2.hasScope('" + scope + "')")
                // 设置所有资源都需要认证后访问(与上面#auth2.hasScope()等价？)
                .anyRequest().authenticated();

    }

    /**
     * 设置可以匿名访问的url
     * @param http
     * @throws Exception
     */
    private void setIgnoreUrls(HttpSecurity http) throws Exception {
        if (ignoreUrls == null) {
            return;
        }

        log.info("用户中心可以匿名访问的url列表：");
        for (String url: ignoreUrls) {
            url = StringUtils.trimAllWhitespace(url);
            log.info(url);
            http.authorizeRequests().antMatchers(url).anonymous();
        }
    }

}
